package com.edu.compile;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.edu.compile.compileresult.CompileLoopResult;
import com.edu.compile.compileresult.CompileResult;
import com.edu.compile.constant.NodeTypeEnum;
import com.edu.compile.exception.CompileException;
import com.edu.compile.template.ModuleTemplateModel;
import com.edu.compile.template.NodeTemplateParse;
import com.edu.compile.template.ftl.module.AfterInterceptorModule;
import com.edu.compile.template.ftl.module.AxiosModule;
import com.edu.compile.template.ftl.module.FsModule;
import com.edu.compile.template.ftl.module.InnerClassModule;
import com.edu.compile.template.ftl.module.RedisPoolModule;
import com.edu.compile.template.ftl.module.ToolsModule;
import com.edu.compile.util.CompilerDispatcher;
import com.edu.compile.util.JsonUtils;
import com.edu.compile.util.ReflectUtils;
import com.edu.model.constant.EventConstant;
import com.edu.model.constant.FileConfig;
import com.edu.model.dto.NodeMetadata;

import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Consumer;

/**
 * @author yanzhao
 * @date 2021/10/26 22:46
 * @since 1.0.0
 */
public abstract class AbstractCompilerParser<C extends Config, T extends NodeTemplateParse> implements Compiler<C, T>, Parser<NodeMetadata, C> {

    /**
     * 添加通用的依赖模块
     *
     * @param abstractCompileResult
     * @see ModuleTemplateModel 子类
     */
    private void addCommonModules(AbstractCompileResult abstractCompileResult) {
        abstractCompileResult.putInnerScript(new AfterInterceptorModule());
        abstractCompileResult.putInnerScript(new FsModule());
        abstractCompileResult.putInnerScript(new InnerClassModule());
        abstractCompileResult.putInnerScript(new RedisPoolModule());
        abstractCompileResult.putInnerScript(new AxiosModule());
        abstractCompileResult.putInnerScript(new ToolsModule());
    }

    /**
     * 编译前动作
     *
     * @param abstractCompileResult
     * @param t
     */
    private void compileBefore(AbstractCompileResult abstractCompileResult, NodeMetadata t) {
        addCommonModules(abstractCompileResult);
        compileBefore4RequireSelf(abstractCompileResult, t);
    }

    /**
     * 模板方法，定义了编译动作的步骤
     *
     * @param abstractCompileResult
     * @param nodeMetadata
     */
    public final void compile0(AbstractCompileResult abstractCompileResult, NodeMetadata nodeMetadata) {
        compileBefore(abstractCompileResult, nodeMetadata);
        compileLoopBefore(abstractCompileResult, nodeMetadata);
        try {
            T template = getTemplate(abstractCompileResult, nodeMetadata);
            registerTemplate(abstractCompileResult, template);
            compile(abstractCompileResult, nodeMetadata, parse(nodeMetadata), template);
            compileNext(abstractCompileResult, nodeMetadata);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CompileException(e, this.getClass());
        }
    }

    @Override
    public T getTemplate(AbstractCompileResult compileResult, NodeMetadata nodeMetadata) {
        try {
            Class<? extends NodeTemplateParse> clazz = NodeTypeEnum.getClass(nodeMetadata.getNodeType());
            Constructor<? extends NodeTemplateParse> constructor = clazz.getConstructor(String.class, String.class, String.class, String.class);
            NodeTemplateParse templateModel = constructor.newInstance(nodeMetadata.getNodeId(), nodeMetadata.getNodeType(), nodeMetadata.getNodeName(), nodeMetadata.getParentNodeId());
            return (T) templateModel;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public void registerTemplate(AbstractCompileResult abstractCompileResult, T nodeTemplateModel) {
        nodeTemplateModel.setLoop(abstractCompileResult.isLoop());
        abstractCompileResult.getTemplatePolymerization().addTemplate(nodeTemplateModel);
    }

    /**
     * 解析NodeMetadata中的config配置
     *
     * @param nodeMetadata
     * @return
     */
    @Override
    public C parse(NodeMetadata nodeMetadata) {
        Type[] actualTypeArguments = ReflectUtils.parseParameterizedType(getClass());
        String config = nodeMetadata.getConfig();
        return (C) JsonUtils.parseObject(config, (Class) actualTypeArguments[0]);
    }

    /**
     * 编译下一节点
     *
     * @param abstractCompileResult
     * @param nodeMetadata
     */
    private void compileNext(AbstractCompileResult abstractCompileResult, NodeMetadata nodeMetadata) {
        List<NodeMetadata> subNodes = abstractCompileResult.getEventContainer().getSubNodes(nodeMetadata.getNodeId());
        Optional.ofNullable(subNodes).ifPresent(new Consumer<List<NodeMetadata>>() {
            @Override
            public void accept(List<NodeMetadata> nodeMetadata) {
                nodeMetadata.forEach(node -> {
                    AbstractCompilerParser compiler = CompilerDispatcher.getCompiler(node);
                    compiler.compile0(abstractCompileResult, node);
                });
            }
        });
    }

    protected void compileLoopBefore(AbstractCompileResult abstractCompileResult, NodeMetadata nodeMetadata) {
        if (abstractCompileResult instanceof CompileLoopResult) {
            CompileLoopResult compileLoopResult = (CompileLoopResult) abstractCompileResult;
            compileLoopResult.putLoopNodeId(nodeMetadata.getNodeId());
            compileLoopResult.setCurrentParseNode(nodeMetadata);
            compileLoopResult.putInitNodeStorageArray("storage['" + nodeMetadata.getNodeId() + "']=[];");
        }
    }

    /**
     * 替代参数
     *
     * @param source     待替换
     * @param methodName 方法名
     * @return 替代
     */
    protected String replaceParam(String source, String methodName) {
        String[] params = source.split(EventConstant.ID_SEPARATOR_VAL);
        String result = "";
        if (params.length == 2) {
            result = "storage['" + methodName + params[0] + "']." + params[1];
        } else if (params.length == 1) {
            result = "storage['" + methodName + params[0] + "']";
        } else {
            throw new IllegalArgumentException("参数替换失败");
        }
        return source.replace(source, result);
    }

    /**
     * 校验是否包含{{数字@字符串}}的内容
     *
     * @param source
     * @return
     */
    public boolean checkMatch(String source) {
        if (source == null) {
            return false;
        }
        source = source.replace(" ", "");
        if ("{{@}}".equals(source)) {
            throw new IllegalArgumentException("{{@}}参数不合法");
        }

        if (source.startsWith("{{") && source.endsWith("}}") && source.contains("@")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 截取{{}}中间的内容
     *
     * @param source
     * @param methodName
     * @return
     */
    protected String findValueFromStorage(String source, String methodName) {
        source = source.substring(2);
        source = source.substring(0, source.length() - 2);
        return replaceParam(source, methodName);
    }

    public String getFilePrefix() {
        return FileConfig.EVENT_PREFIX;
    }

    public String getFileSuffix() {
        return FileConfig.JS_SUFFIX;
    }

    protected void compileBefore4RequireSelf(AbstractCompileResult abstractCompileResult, NodeMetadata nodeMetadata) {
        String nodeType = nodeMetadata.getNodeType();
        switch (nodeType) {
            case NodeTypeEnum.NODE_SYMBOL: {
                abstractCompileResult.addScriptRequire("const " + nodeMetadata.getNodeId() + "=" + " require('./" + nodeMetadata.getNodeId() + ".js');");
                break;
            }
            default: {
                break;
            }
        }
    }

    protected String findValueFromStorage(AbstractCompileResult abstractCompileResult, String source, String methodName) {
        source = source.substring(2);
        source = source.substring(0, source.length() - 2);
        return replaceParam(abstractCompileResult, source, methodName);
    }

    protected String replaceParam(AbstractCompileResult abstractCompileResult, String source, String methodPrefix) {
        String[] params = source.split(EventConstant.ID_SEPARATOR_VAL);
        String result = "";
        String _index = checkLoop(abstractCompileResult, params[0]) ? getIndexSymbol(abstractCompileResult) : "";
        String x = _index.equals("") ? "'" : "_'";
        if (params.length == 2) {
            result = "storage['" + methodPrefix + params[0] + x + _index + "]." + params[1];
        } else if (params.length == 1) {
            result = "storage['" + methodPrefix + params[0] + x + _index + "]";
        } else {
            throw new IllegalArgumentException("参数替换失败");
        }
        return source.replace(source, result);
    }

    private boolean checkLoop(AbstractCompileResult abstractCompileResult, String nodeId) {
        return (abstractCompileResult.isLoop() && ((CompileLoopResult) abstractCompileResult).isLoopNodeId(Integer.valueOf(nodeId))) || (abstractCompileResult.isLoop() && ((CompileLoopResult) abstractCompileResult).getLoopBeginNodeDto().getNodeId().equals(nodeId));
    }

    private String getIndexSymbol(AbstractCompileResult abstractCompileResult) {
        if (abstractCompileResult.checkCurrentParseNode(null)) {
            return EventConstant.LoopConstant.ADD_LOOP_INDEX_SYMBOL;
        } else {
            return EventConstant.LoopConstant.ADD_INDEX_SYMBOL;
        }
    }

    /**
     * 解析json参数，可解析多层json，递归解析{{1@deviceId}}字符串
     * {"apiUrl":"http://localhost:8080/serveOpe/queryForm","encode":"UTF-8","method":"GET","params":"{\"eventName\":\"1asd\",\"eventId\":\"{{1@deviceId}}\",\"projectId\":412123,\"detail\":\"{\\\"testName\\\":\\\"测试\\\",\\\"version\\\":\\\"{{1@productId}}\\\"}\"}","apiType":0,"contentType":"application/x-www-form-urlencoded"}
     *
     * @param params
     * @param methodName
     * @param prefix
     */
    public Map<String, String> parseParams(AbstractCompileResult abstractCompileResult, String params, String methodName, String prefix) {
        Map<String, Object> paramMap = JSON.parseObject(params, Map.class);
        Iterator<Map.Entry<String, Object>> it = paramMap.entrySet().iterator();
        Map<String, String> result = new TreeMap<>();
        while (it.hasNext()) {
            Map.Entry<String, Object> next = it.next();
            String key = next.getKey();
            String value = next.getValue().toString();
            if (checkMatch(value)) {
                value = findValueFromStorage(abstractCompileResult, value, methodName);
                result.put(prefix + key, value);
            } else {
                result.put(prefix + key, "'" + value + "'");
            }
        }
        return result;
    }

    /**
     * 递归解析json参数，可解析多层json，递归解析{{1@deviceId}}字符串
     * {"apiUrl":"http://localhost:8080/serveOpe/queryForm","encode":"UTF-8","method":"GET","params":"{\"eventName\":\"1asd\",\"eventId\":\"{{1@deviceId}}\",\"projectId\":412123,\"detail\":\"{\\\"testName\\\":\\\"测试\\\",\\\"version\\\":\\\"{{1@productId}}\\\"}\"}","apiType":0,"contentType":"application/x-www-form-urlencoded"}
     *
     * @param params
     * @param methodName
     * @param prefix
     * @param result
     */
    public void parseParams(AbstractCompileResult abstractCompileResult, String params, String methodName, String prefix, Map<String, String> result) {
        Map<String, Object> paramMap = JSON.parseObject(params, Map.class);

        Iterator<Map.Entry<String, Object>> it = paramMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Object> next = it.next();
            String key = next.getKey();
            Object v = next.getValue();
            if (v == null) {
                result.put(prefix + key, null);
            } else {
                String value = v.toString();
                if (checkMatch(value)) {
                    value = findValueFromStorage(abstractCompileResult, value, methodName);
                    result.put(prefix + key, value);
                } else {
                    result.put(prefix + key, "'" + value + "'");
                }
            }
        }

        return;
    }

    public void buildJsonParam(StringBuilder buffer, Map<String, String> result, String prefix) {
        List<String> check = new ArrayList<>();
        Iterator<Map.Entry<String, String>> it = result.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            String key = next.getKey();
            String[] paramNames = key.split("\\.");
            if (paramNames.length > 2) {
                String init = prefix;
                for (int i = 1; i < paramNames.length - 1; i++) {
                    init = init + "." + paramNames[i];
                    if (!check.contains(init)) {
                        buffer.append(init).append("={};");
                        check.add(init);
                    }
                }
            }
            buffer.append(next.getKey()).append("=").append(next.getValue()).append(";");
        }
    }

    /**
     * 构建form表单数据，post和get都用此方法
     * let content = {};
     * content['detail.testName'] = '测试';
     * content['detail.version'] = storage['func_16_1'].productId;
     * content['eventId'] = storage['func_16_1'].deviceId;
     * content['eventName'] = '1asd';
     * content['projectId'] = '412123';
     *
     * @param buffer
     * @param result
     * @param prefix
     */
    public void buildFormParam(StringBuilder buffer, Map<String, String> result, String prefix) {
        Iterator<Map.Entry<String, String>> it = result.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            String key = next.getKey();
            String value = next.getValue();
            buffer.append(prefix).append("['").append(key).append("']").append("=").append(value).append(";");
        }
    }

    public boolean checkArray(Object object) {
        if (object == null) {
            return false;
        }

        try {
            if (object instanceof JSONArray) {
                return true;
            }
        } catch (Exception e) {

        }

        return object.getClass().isArray();
    }

    // 整合判断基本数据类型和包装类型的方法
    public boolean isPrimitive(Object object) {
        Class<?> clazz = object.getClass();
        try {
            // 判断基本数据类型
            if (clazz.isPrimitive()) {
                return true;
            }
            // 判断包装类型
            if (clazz.getField("TYPE") != null && ((Class) (clazz.getField("TYPE").get(null))).isPrimitive()) {
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    public AbstractCompileResult newCompileResult() {
        return new CompileResult();
    }
}
